<?php

namespace app\admin\service;

use hg\apidoc\annotation as Apidoc;

class MonitorService
{
    /**
     * 获取cpu信息
     * @return array
     */
    #[Apidoc\Returned("cpu", type: "object", desc: "CPU信息", children: [
        ['name' => 'cpu_name', 'type' => 'string', 'desc' => 'CPU名称'],
        ['name' => 'physical_cores', 'type' => 'string', 'desc' => '物理核心数'],
        ['name' => 'logical_cores', 'type' => 'string', 'desc' => '逻辑核心数'],
        ['name' => 'cache_size_mb', 'type' => 'string', 'desc' => '缓存大小(MB)'],
        ['name' => 'cpu_usage_percentage', 'type' => 'string', 'desc' => 'CPU使用率(%)'],
        ['name' => 'free_cpu_percentage', 'type' => 'string', 'desc' => '可用CPU百分比(%)'],
    ])]
    public function getCpuInfo(): array
    {
        try {
            if (stristr(PHP_OS, 'WIN')) {
                $cpu = $this->executeShellCommand('wmic cpu get LoadPercentage | findstr /V "LoadPercentage"', 3);
                $cpu = intval(trim($cpu ?? '0'));
                $cache = $this->executeShellCommand('wmic cpu get L3CacheSize | findstr /V "L3CacheSize"', 3);
                $cache = trim($cache ?? '');
                if ($cache == '') {
                    $cache = $this->executeShellCommand('wmic cpu get L2CacheSize | findstr /V "L2CacheSize"', 3);
                    $cache = trim($cache ?? '');
                }
                if ($cache != '') {
                    $cache = [0, intval($cache) * 1024];
                }
            } else {
                $cpu = $this->getCpuUsage();
                $cacheInfo = $this->executeShellCommand('cat /proc/cpuinfo | grep "cache size"', 3);
                preg_match('/(\d+)/', $cacheInfo ?? '', $cache);
                if (count($cache) == 0) {
                    // aarch64 有可能是arm架构
                    $cache = trim($this->executeShellCommand("lscpu | grep L3 | awk '{print \$NF}'", 3) ?? '');
                    if ($cache == '') {
                        $cache = trim($this->executeShellCommand("lscpu | grep L2 | awk '{print \$NF}'", 3) ?? '');
                    }
                    if ($cache != '') {
                        $cache = [0, intval(str_replace(['K', 'B'], '', strtoupper($cache)))];
                    }
                }
            }
            return [
                'cpu_name' => $this->getCpuName(), // CPU 名称
                'physical_cores' => (int)$this->getCpuPhysicsCores(), // 物理核心数
                'logical_cores' => (int)$this->getCpuLogicCores(), // 逻辑核心数
                'cache_size_mb' => $cache[1] ? $cache[1] / 1024 : 0, // 缓存大小（MB）
                'cpu_usage_percentage' => $cpu, // CPU 使用率（%）
                'free_cpu_percentage' => round(100 - $cpu, 2), // 可用 CPU 百分比（%）
            ];
        } catch (\Exception $e) {
            return [
                'cpu_name' => '获取失败',
                'physical_cores' => '获取失败',
                'logical_cores' => '获取失败',
                'cache_size_mb' => '获取失败',
                'cpu_usage_percentage' => '获取失败',
                'free_cpu_percentage' => '获取失败',
            ];
        }
    }

    /**
     * 获取内存信息
     * @return array
     */
    #[Apidoc\Returned("memory", type: "object", desc: "内存信息", children: [
        ['name' => 'total_memory', 'type' => 'string', 'desc' => '总内存'],
        ['name' => 'available_memory', 'type' => 'string', 'desc' => '可用内存'],
        ['name' => 'used_memory', 'type' => 'string', 'desc' => '已使用内存'],
        ['name' => 'php_memory_usage', 'type' => 'string', 'desc' => 'PHP内存使用量'],
        ['name' => 'memory_usage_rate', 'type' => 'string', 'desc' => '内存使用率'],
    ])]
    public function getMemoryInfo(): array
    {
        $result = [];
        if (stristr(PHP_OS, 'WIN')) {
            $cap = $this->executeShellCommand('wmic Path Win32_PhysicalMemory Get Capacity | findstr /V "Capacity"', 3);
            $caps = array_filter(array_map('trim', explode("\n", $cap)));
            $totalMemory = round(array_sum(array_map('intval', $caps)) / 1024 / 1024 / 1024, 2);
            $result['total_memory'] = $totalMemory . ' GB'; // 总内存
            $free = $this->executeShellCommand('wmic OS get FreePhysicalMemory | findstr /V "FreePhysicalMemory"', 3);
            $availableMemory = round(intval(trim($free)) / 1024 / 1024, 2);
            $result['available_memory'] = $availableMemory . ' GB'; // 可用内存
            $usedMemory = round($totalMemory - $availableMemory, 2);
            $result['used_memory'] = $usedMemory . ' GB'; // 使用内存
        } else {
            $memInfo = $this->executeShellCommand('cat /proc/meminfo', 3);
            $memInfoArray = array_filter(array_map('trim', explode("\n", $memInfo)));
            $total = $available = 0;
            foreach ($memInfoArray as $line) {
                if (str_starts_with($line, 'MemTotal:')) {
                    preg_match('/(\d+)/', $line, $matches);
                    $total = intval($matches[1]);
                }
                if (str_starts_with($line, 'MemAvailable:')) {
                    preg_match('/(\d+)/', $line, $matches);
                    $available = intval($matches[1]);
                }
            }
            $totalMemory = round($total / 1024 / 1024, 2);
            $result['total_memory'] = $totalMemory . ' GB'; // 总内存
            $availableMemory = round($available / 1024 / 1024, 2);
            $result['available_memory'] = $availableMemory . ' GB'; // 可用内存
            $usedMemory = round(($total - $available) / 1024 / 1024, 2);
            $result['used_memory'] = $usedMemory . ' GB'; // 使用内存
        }
        // PHP 使用内存（MB）
        $phpMemoryUsage = round(memory_get_usage(true) / 1024 / 1024, 2);
        $result['php_memory_usage'] = $phpMemoryUsage . ' MB';
        // 计算内存使用率
        if ($totalMemory > 0) {
            $memoryUsageRate = sprintf('%.2f', ($usedMemory / $totalMemory) * 100);
            $result['memory_usage_rate'] = $memoryUsageRate . '%';
        } else {
            $result['memory_usage_rate'] = '0.00%';
        }
        return $result;
    }

    /**
     * 获取磁盘信息
     * @return array
     */
    #[Apidoc\Returned("disk", type: "array", desc: "磁盘信息", children: [
        ['name' => 'filesystem', 'type' => 'string', 'desc' => '文件系统'],
        ['name' => 'size', 'type' => 'string', 'desc' => '总大小'],
        ['name' => 'used', 'type' => 'string', 'desc' => '已使用'],
        ['name' => 'available', 'type' => 'string', 'desc' => '可用空间'],
        ['name' => 'use_percentage', 'type' => 'string', 'desc' => '使用率'],
        ['name' => 'mounted_on', 'type' => 'string', 'desc' => '挂载点'],
    ])]
    public function getDiskInfo(): array
    {
        $disk = [];
        if (stristr(PHP_OS, 'WIN')) {
            $drives = $this->executeShellCommand('wmic logicaldisk get size,freespace,caption', 5);
            if ($drives === false) {
                return $disk;
            }
            $lines = explode("\n", trim($drives));
            foreach ($lines as $line) {
                if (preg_match('/^([A-Z]:)/', $line, $matches)) {
                    $parts = preg_split('/\s+/', trim($line));
                    if (count($parts) >= 3) {
                        $disk[] = [
                            'filesystem' => $matches[1],
                            'size' => $this->formatBytes(intval($parts[1])),
                            'available' => $this->formatBytes(intval($parts[2])),
                            'used' => $this->formatBytes(intval($parts[1]) - intval($parts[2])),
                            'use_percentage' => sprintf('%.2f', (intval($parts[1]) - intval($parts[2])) / intval($parts[1]) * 100) . '%',
                            'mounted_on' => $matches[1],
                        ];
                    }
                }
            }
        } else {
            $diskInfo = $this->executeShellCommand('df -h', 5);
            if ($diskInfo === false) {
                return $disk;
            }
            $lines = explode("\n", trim($diskInfo));
            foreach ($lines as $line) {
                if (preg_match('/^\/dev\/\w+/', $line)) {
                    $parts = preg_split('/\s+/', $line);
                    $disk[] = [
                        'filesystem' => $parts[0],
                        'size' => $parts[1],
                        'used' => $parts[2],
                        'available' => $parts[3],
                        'use_percentage' => $parts[4],
                        'mounted_on' => $parts[5],
                    ];
                }
            }
        }
        return $disk;
    }

    /**
     * 获取PHP环境信息
     * @return array
     */
    #[Apidoc\Returned("php", type: "object", desc: "PHP环境信息", children: [
        ['name' => 'php_version', 'type' => 'string', 'desc' => 'PHP版本'],
        ['name' => 'os', 'type' => 'string', 'desc' => '操作系统'],
        ['name' => 'project_path', 'type' => 'string', 'desc' => '项目路径'],
        ['name' => 'memory_limit', 'type' => 'string', 'desc' => '内存限制'],
        ['name' => 'max_execution_time', 'type' => 'string', 'desc' => '最大执行时间'],
        ['name' => 'error_reporting', 'type' => 'string', 'desc' => '错误报告级别'],
        ['name' => 'display_errors', 'type' => 'string', 'desc' => '是否显示错误'],
        ['name' => 'upload_max_filesize', 'type' => 'string', 'desc' => '最大上传文件大小'],
        ['name' => 'post_max_size', 'type' => 'string', 'desc' => 'POST数据最大大小'],
        ['name' => 'extension_dir', 'type' => 'string', 'desc' => '扩展目录'],
        ['name' => 'loaded_extensions', 'type' => 'string', 'desc' => '已加载的扩展'],
    ])]
    public function getPhpInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'project_path' => BASE_PATH,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'error_reporting' => ini_get('error_reporting'),
            'display_errors' => ini_get('display_errors'),
            'upload_max_filesize' => ini_get('upload_max_filesize'),
            'post_max_size' => ini_get('post_max_size'),
            'extension_dir' => ini_get('extension_dir'),
            'loaded_extensions' => implode(', ', get_loaded_extensions()),
        ];
    }

    /**
     * 获取CPU名称
     * @return string
     */
    private function getCpuName(): string
    {
        if (PHP_OS == 'Linux') {
            $cpuInfo = $this->executeShellCommand('cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c', 3);
            preg_match('/^\s+\d\s+(.+)/', $cpuInfo ?? '', $matches);
            if (count($matches) == 0) {
                // aarch64 有可能是arm架构
                $name = trim($this->executeShellCommand("lscpu| grep Architecture | awk '{print $2}'", 3) ?? '');
                if ($name != '') {
                    $mfMhz = trim($this->executeShellCommand("lscpu| grep 'MHz' | awk '{print \$NF}' | head -n1", 3) ?? '');
                    $mfGhz = trim($this->executeShellCommand("lscpu| grep 'GHz' | awk '{print \$NF}' | head -n1", 3) ?? '');
                    if ($mfMhz == '' && $mfGhz == '') {
                        return $name;
                    } else if ($mfGhz != '') {
                        return $name . ' @ ' . $mfGhz . 'GHz';
                    } else if ($mfMhz != '') {
                        return $name . ' @ ' . round(intval($mfMhz) / 1000, 2) . 'GHz';
                    }
                } else {
                    return '未知';
                }
            }
            return $matches[1] ?? "未知";
        } else {
            $name = $this->executeShellCommand('wmic cpu get Name | findstr /V "Name"', 3);
            return trim($name);
        }
    }

    /**
     * 获取cpu物理核心数
     * @return string
     */
    private function getCpuPhysicsCores(): string
    {
        if (PHP_OS == 'Linux') {
            $num = str_replace("\n", '', $this->executeShellCommand('cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l', 3));
            return intval($num) == 0 ? '1' : $num;
        } else {
            $num = $this->executeShellCommand('wmic cpu get NumberOfCores | findstr /V "NumberOfCores"', 3);
            $num = trim($num ?? '1');
            $nums = explode("\n", $num);
            $num = 0;
            foreach ($nums as $n) {
                $num += intval(trim($n));
            }
            return strval($num);
        }
    }

    /**
     * 获取cpu逻辑核心数
     * @return string
     */
    private function getCpuLogicCores(): string
    {
        if (PHP_OS == 'Linux') {
            return str_replace("\n", '', $this->executeShellCommand('cat /proc/cpuinfo |grep "processor"|wc -l', 3));
        } else {
            $num = $this->executeShellCommand('wmic cpu get NumberOfLogicalProcessors | findstr /V "NumberOfLogicalProcessors"', 3);
            $num = trim($num ?? '1');
            $nums = explode("\n", $num);
            $num = 0;
            foreach ($nums as $n) {
                $num += intval(trim($n));
            }
            return strval($num);
        }
    }

    /**
     * 获取CPU使用率
     * @return string
     */
    private function getCpuUsage(): string
    {
        $start = $this->calculationCpu();
        // 0.1秒
        usleep(100000);
        $end = $this->calculationCpu();
        $totalStart = $start['total'];
        $totalEnd = $end['total'];
        $timeStart = $start['time'];
        $timeEnd = $end['time'];
        // 避免除零错误
        if (($totalEnd - $totalStart) == 0) {
            return '0.00';
        }
        return sprintf('%.2f', ($timeEnd - $timeStart) / ($totalEnd - $totalStart) * 100);
    }

    /**
     * 计算CPU
     * @return array
     */
    private function calculationCpu(): array
    {
        $mode = '/(cpu)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)[\s]+([0-9]+)/';
        $string = $this->executeShellCommand('cat /proc/stat | grep cpu', 2);
        preg_match_all($mode, $string, $matches);
        if (empty($matches[2])) {
            return ['total' => 1, 'time' => 1];
        }
        $total = $matches[2][0] + $matches[3][0] + $matches[4][0] + $matches[5][0] + $matches[6][0] + $matches[7][0] + $matches[8][0] + $matches[9][0];
        $time = $matches[2][0] + $matches[3][0] + $matches[4][0] + $matches[6][0] + $matches[7][0] + $matches[8][0] + $matches[9][0];
        return ['total' => $total, 'time' => $time];
    }

    /**
     * 格式化字节可读格式
     * @param $bytes
     * @param int $decimals
     * @return string
     */
    private function formatBytes($bytes, int $decimals = 2): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $factor = floor((strlen($bytes) - 1) / 3);
        return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$units[$factor];
    }

    /**
     * 执行shell命令并设置超时时间
     * @param string $command
     * @param int $timeout
     * @return false|string
     */
    private function executeShellCommand(string $command, int $timeout = 5): false | string
    {
        try {
            // 使用 proc_open 来更好地控制进程
            $descriptorspec = [
                0 => ["pipe", "r"],  // stdin
                1 => ["pipe", "w"],  // stdout
                2 => ["pipe", "w"],   // stderr
            ];
            $process = proc_open($command, $descriptorspec, $pipes);
            if (is_resource($process)) {
                // 设置非阻塞模式
                stream_set_blocking($pipes[1], false);
                stream_set_blocking($pipes[2], false);
                $startTime = time();
                $output = '';
                // 等待进程结束或超时
                while (time() - $startTime < $timeout) {
                    $status = proc_get_status($process);
                    if (!$status['running']) {
                        break;
                    }
                    // 读取输出
                    $output .= stream_get_contents($pipes[1]);
                    usleep(100000); // 0.1秒
                }
                // 关闭进程
                fclose($pipes[0]);
                fclose($pipes[1]);
                fclose($pipes[2]);
                proc_close($process);
                return $output;
            }
        } catch (\Exception $e) {
            // 如果出现异常，返回默认值
            return false;
        }
        return false;
    }
}